Skip to content

feat(W-23159740): per-user persistent feature flags (iOS)#4086

Open
wmathurin wants to merge 8 commits into
forcedotcom:devfrom
wmathurin:W-21167151-feature-flags-per-user
Open

feat(W-23159740): per-user persistent feature flags (iOS)#4086
wmathurin wants to merge 8 commits into
forcedotcom:devfrom
wmathurin:W-21167151-feature-flags-per-user

Conversation

@wmathurin

Copy link
Copy Markdown
Contributor

Summary

  • Adds per-user feature flag storage to SFUserAccount via persistedFeatureFlags (NSSet, serialized with NSKeyedArchiver into UserAccount.plist)
  • SFSDKAppFeatureMarkers gains per-user register/unregister/query methods; +appFeaturesForUser: returns global ∪ per-user union; +loadPersistedFeatures:forUser: hydrates in-memory state without writeback
  • SalesforceSDKManager.userAgentString:qualifier:forUser: resolves per-user flags for UA construction; hydratePerUserFeatureFlags populates in-memory state at SDK init
  • SFUserAccountManager.finalizeAuthCompletion: promotes BW/WD/QR transient global flags to per-user after authentication completes
  • SmartStore (SU) and MobileSync (MS) flags now registered per-user
  • 6 new unit tests in SFSDKAppFeatureMarkersTests

GUS Stories

  • W-23159740 (iOS per-user feature flags)
  • W-21167151 (spike)

Test plan

  • SFSDKAppFeatureMarkersTests — 9 tests, 0 failures
  • SalesforceSDKManagerTests — all existing tests pass
  • Build succeeds: xcodebuild build -scheme SalesforceSDKCore -sdk iphonesimulator
  • Integration tests (require sandbox credentials — run in CI)

Implements per-user feature flag storage and hydration so each
user account's active features are accurately reflected in the
User-Agent string across app restarts.

- SFSDKAppFeatureMarkers: +registerAppFeature:forUser:, +unregisterAppFeature:forUser:,
  +appFeaturesForUser: (union of global + per-user), +loadPersistedFeatures:forUser:
- SFUserAccount: persistedFeatureFlags property (NSSet<NSString*>),
  serialized via NSKeyedArchiver into UserAccount.plist
- SalesforceSDKManager: userAgentString:qualifier:forUser: unions global + per-user flags;
  hydratePerUserFeatureFlags called from sharedManager dispatch_once
- SFUserAccountManager: finalizeAuthCompletion promotes BW/WD/QR transient
  global flags to per-user after login completes
- SFSmartStore: SU flag now registered per-user
- SFMobileSyncSyncManager: MS flag now registered per-user
- SFSDKAppFeatureMarkersTests: 6 new tests covering isolation, union,
  nil-user fallback, per-user unregister, hydration, and persistence
@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
1 Warning
⚠️ Static Analysis found an issue with one or more files you modified. Please fix the issue(s).

Clang Static Analysis Issues

File Type Category Description Line Col
SFUserAccount Nullability Memory error nil assigned to a pointer which is expected to have non-null value 266 22
SFUserAccount Nullability Memory error nil passed to a callee that requires a non-null 1st parameter 450 18
SFUserAccount Nullability Memory error nil passed to a callee that requires a non-null 1st parameter 452 18
SFUserAccountManager Nullability Memory error Null passed to a callee that requires a non-null 2nd parameter 1579 15
SFUserAccountManager Nullability Memory error Null passed to a callee that requires a non-null 2nd parameter 1594 15
SFUserAccountManager Nullability Memory error nil passed to a callee that requires a non-null 2nd parameter 2267 13
SalesforceSDKManager Nil value used as mutex for @synchronized() (no synchronization will occur) Logic error Nil value used as mutex for @synchronized() (no synchronization will occur) 145 5
SalesforceSDKManager Nil value used as mutex for @synchronized() (no synchronization will occur) Logic error Nil value used as mutex for @synchronized() (no synchronization will occur) 157 5
SFSmartStore Nullability Memory error nil passed to a callee that requires a non-null 2nd parameter 583 28
SFSmartStore Nullability Memory error nil passed to a callee that requires a non-null 2nd parameter 596 27
SFSmartStore NSError** null dereference Coding conventions (Apple) Potential null dereference. According to coding standards in 'Creating and Returning NSError Objects' the parameter may be null 1349 16
SFSmartStore Nullability Memory error nil passed to a callee that requires a non-null 1st parameter 1425 28

Generated by 🚫 Danger

@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.30189% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.70%. Comparing base (ec26a56) to head (34cd737).
⚠️ Report is 5 commits behind head on dev.

Files with missing lines Patch % Lines
...SDKCore/Classes/UserAccount/SFUserAccountManager.m 0.00% 23 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #4086      +/-   ##
==========================================
- Coverage   70.69%   65.70%   -5.00%     
==========================================
  Files         246      246              
  Lines       21501    21580      +79     
==========================================
- Hits        15201    14179    -1022     
- Misses       6300     7401    +1101     
Components Coverage Δ
Analytics 70.78% <ø> (ø)
Common 70.79% <ø> (-0.19%) ⬇️
Core 57.83% <77.88%> (-7.69%) ⬇️
SmartStore 73.44% <100.00%> (-0.16%) ⬇️
MobileSync 88.85% <100.00%> (+0.11%) ⬆️
Files with missing lines Coverage Δ
...bileSync/Classes/Manager/SFMobileSyncSyncManager.m 76.06% <100.00%> (ø)
...rceSDKCore/Classes/Common/SFSDKAppFeatureMarkers.m 100.00% <100.00%> (ø)
...forceSDKCore/Classes/Common/SalesforceSDKManager.m 72.10% <100.00%> (-1.03%) ⬇️
...esforceSDKCore/Classes/UserAccount/SFUserAccount.m 78.46% <100.00%> (-2.16%) ⬇️
libs/SmartStore/SmartStore/Classes/SFSmartStore.m 80.47% <100.00%> (-0.36%) ⬇️
...SDKCore/Classes/UserAccount/SFUserAccountManager.m 43.19% <0.00%> (-19.15%) ⬇️

... and 41 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

Copy link
Copy Markdown
TestsPassed ✅SkippedFailed
SmartStore iOS ^26 Test Results177 ran177 ✅
TestResult
No test annotations available

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
TestsPassed ☑️SkippedFailed ❌️
SalesforceSDKCore iOS ^26 Test Results673 ran651 ✅22 ❌
TestResult
SalesforceSDKCore iOS ^26 Test Results
SalesforceRestAPITests.testRestUrlForCommunityUrl❌ failure
SalesforceRestAPITests.testRestUrlForLoginServiceHost❌ failure
SalesforceRestAPITests.testSalesforceFullUrlPath❌ failure
SalesforceRestAPITests.testSObjectTreeRequest❌ failure
SalesforceRestAPITests.testSOQLQueryWithBatchSize❌ failure
SalesforceRestAPITests.testSOSL❌ failure
SalesforceRestAPITests.testUpdateNotificationRequestPath❌ failure
SalesforceRestAPITests.testUpdateReadNotifications❌ failure
SalesforceRestAPITests.testUpdateSeenNotifications❌ failure
SalesforceRestAPITests.testUpdateWithIfUnmodifiedSince❌ failure
SalesforceRestAPITests.testUploadBatchDetailsDeleteFiles❌ failure
SalesforceRestAPITests.testUploadBatchDetailsDeleteFilesCommunity❌ failure
SalesforceRestAPITests.testUploadDetailsDeleteFile❌ failure
SalesforceRestAPITests.testUploadDetailsDeleteFileWithCommunity❌ failure
SalesforceRestAPITests.testUploadDownloadDeleteFile❌ failure
SalesforceRestAPITests.testUploadDownloadDeleteFileWithCommunity❌ failure
SalesforceRestAPITests.testUploadOwnedFilesDelete❌ failure
SalesforceRestAPITests.testUploadProfilePhoto❌ failure
SalesforceRestAPITests.testUploadProfilePhotoCommunity❌ failure
SalesforceRestAPITests.testUploadShareFileSharesSharedFilesUnshareDelete❌ failure
SalesforceRestAPITests.testUpsert❌ failure
SalesforceRestAPITests.testUpsertWithBogusExternalIdField❌ failure

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
TestsPassed ☑️SkippedFailed ❌️
MobileSync iOS ^18 Test Results234 ran226 ✅8 ❌
TestResult
MobileSync iOS ^18 Test Results
SyncManagerTests.testCustomSyncUpWithLocallyUpdatedRecordsWithoutOverwrite❌ failure
SyncUpTargetTests.testSyncUpWithLocallyUpdatedRecords❌ failure
SyncUpTargetTests.testSyncUpWithLocallyUpdatedRecordsWithoutOverwrite❌ failure
SyncUpTargetTests.testSyncUpWithLocallyUpdatedRemotelyDeletedRecords❌ failure
SyncUpTargetTests.testSyncUpWithLocallyUpdatedRemotelyDeletedRecordsWithoutOverwrite❌ failure
SyncUpTargetTests.testSyncUpWithNoLocalUpdates❌ failure
SyncUpTargetTests.testSyncUpWithNoType❌ failure
SyncUpTargetTests.testSyncUpWithUpdateFieldList❌ failure

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
TestsPassed ☑️SkippedFailed ❌️
MobileSync iOS ^26 Test Results234 ran232 ✅2 ❌
TestResult
MobileSync iOS ^26 Test Results
ParentChildrenSyncTests.testSyncUpLocallyDeletedParentUpdatedChildRemotelyUpdatedParent❌ failure
SyncManagerTests.testCleanResyncGhostsForMRUTarget❌ failure

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
TestsPassed ☑️SkippedFailed ❌️
SalesforceSDKCore iOS ^18 Test Results673 ran667 ✅6 ❌
TestResult
SalesforceSDKCore iOS ^18 Test Results
SalesforceRestAPITests.testUpdateWithIfUnmodifiedSince❌ failure
SalesforceRestAPITests.testUploadDownloadDeleteFile❌ failure
SalesforceRestAPITests.testUploadOwnedFilesDelete❌ failure
SalesforceRestAPITests.testUploadProfilePhoto❌ failure
SalesforceRestAPITests.testUploadShareFileSharesSharedFilesUnshareDelete❌ failure
SalesforceRestAPITests.testUpsert❌ failure

Comment thread libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/SFSDKAppFeatureMarkers.m Outdated
Comment thread libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Common/SFSDKAppFeatureMarkers.m Outdated
- Replace custom SFSDKUserKey() with SFKeyForUserAndScope(user, SFUserAccountScopeUser)
  to reuse the existing codebase convention for user-scoped keys
- Capture per-user flag snapshot inside dispatch_sync block before
  assigning to user.persistedFeatureFlags, eliminating the map read
  outside the queue's protection
@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
TestsPassedSkippedFailed ❌️
AuthFlowTester UI Test Results all1 ran1 ❌
TestResult
AuthFlowTester UI Test Results all
AuthFlowTesterUITests.xctest
LegacyLoginTests.testCAOpaque_DefaultScopes_WebServerFlow()❌ failure

…tion to UI tests

- UserCredentialsView: add "SDK" section with User Agent row and JSON export
- AuthFlowTesterMainPageObject: add getUserAgent() via JSON export
- BaseAuthFlowTester: add validateUserAgent(), loginOtherUser(), isMultiUser param
- MultiUserLoginTests: add testAdvancedAuthUser_HasBWFlag_RegularAuthUser_DoesNot
- SFUserAccountManager: fix BW/WD global flag bleed by clearing global flags after
  promoting to per-user in finalizeAuthCompletion (matches existing QR pattern)
@github-actions

Copy link
Copy Markdown
TestsPassed ✅SkippedFailed
SmartStore iOS ^18 Test Results177 ran177 ✅
TestResult
No test annotations available

…Agent into public/private overloads

validateUserAgent(loginHost:...) now fetches the UA once and delegates to a
private overload that takes ua: String. validate() calls the private overload
using credentials already loaded, eliminating a second export-button round-trip.
…ode paths

- SFSDKAppFeatureMarkersTests: appFeaturesForUser:nil equivalence, unregister updates persistedFeatureFlags
- SFUserAccountManagerTests: persistedFeatureFlags NSKeyedArchiver encode/decode roundtrip
- SalesforceSDKManagerTests: userAgentString:forUser: with per-user flag, nil-user fallback, hydratePerUserFeatureFlags
- Expose hydratePerUserFeatureFlags in SalesforceSDKManager+Internal.h for testability
…e-deriving from credentials.domain

By auth completion, handleCustomDomainUpdateWithLoginHint:myDomain: has replaced
credentials.domain with the resolved org domain, which no longer contains '/discovery'.
Re-evaluating isDiscoveryDomain at that point always returns false, so the WD flag
was never promoted to per-user. Use the transient global flag (already set by
SFOAuthCoordinator at auth start) as the source of truth, matching the QR flag pattern.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants